﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;

namespace Andwp.AudioPlayer
{

    /// <summary>
    /// 音频播放实现。
    /// </summary>
    unsafe public partial class Player : IDisposable
    {
        /// <summary>
        /// 设备句柄句柄。
        /// </summary>
        private IntPtr m_waveOutPtr;

        /// <summary>
        /// 未处理的播放计数。
        /// </summary>
        private int m_unhandleDataCounter;

        /// <summary>
        /// 获取未处理的播放数据计数。
        /// </summary>
        public int UnhandleDataCounter
        {
            get { return m_unhandleDataCounter; }
        }
        /// <summary>
        /// 允许最大未处理数据包 。
        /// </summary>
        private int m_maxAllowUnhandleData = 100;
        /// <summary>
        /// 获取或设置允许最大未处理数据包，默认100包，超过不处理。
        /// </summary>
        public int MaxAllowUnhandleData
        {
            get { return m_maxAllowUnhandleData; }
            set { m_maxAllowUnhandleData = value; }
        }

        /// <summary>
        /// 设备处理是否关闭。
        /// </summary>
        private bool m_closed = false;

        /// <summary>
        /// 音频输出回调。
        /// </summary>
        private WinmmInterop.WaveOutProc m_waveOutPtrCallback;

        /// <summary>
        /// 获取当前系统内所有播放设备信息。
        /// </summary>
        /// <returns>返回设备集合</returns>
        public static List<WaveDeviceInfo> GetDevices(out string error)
        {
            List<WaveDeviceInfo> waveInfos = new List<WaveDeviceInfo>();
            uint nDevs = WinmmInterop.waveOutGetNumDevs();
            error = string.Empty;
            // 遍历设备ID
            for (uint i = 0; i < nDevs; i++)
            {
                WinmmInterop.WAVEOUTCAPS waveCaps = new WinmmInterop.WAVEOUTCAPS();
                waveCaps.szPname = new byte[32 * 2];
                UIntPtr iId = (UIntPtr)i;
                uint nSize = (uint)Marshal.SizeOf(typeof(WinmmInterop.WAVEOUTCAPS));
                uint nErr = WinmmInterop.waveOutGetDevCaps(iId, ref waveCaps, nSize);
                if (nErr != 0)
                {
                    error = string.Format("获取音频设备出错：错误码{0}", nErr);
                    break;
                }
                WaveDeviceInfo info = new WaveDeviceInfo()
                {
                    DeviceID = i,
                    Name = Encoding.Unicode.GetString(waveCaps.szPname).Replace("\0", "...")
                };
                waveInfos.Add(info);
            }
            return waveInfos;
        }

        /// <summary>
        /// 构造。
        /// </summary>
        public Player()
        {
            m_waveOutPtrCallback = new WinmmInterop.WaveOutProc(ProcCallback);
        }

        /// <summary>
        /// 初始化指定设备。
        /// </summary>
        /// <param name="chn">通道，1：单通道；2：双通道</param>
        /// <param name="smpPreSecond">采样率。</param>
        /// <param name="bitPerSample">采样位数；取值：8；16；32等。</param>
        /// <param name="devId">设备ID索引，若填0表示当前默认设备。</param>
        /// <returns>初始化成功，返回true</returns>
        public bool Init(UInt16 chn, uint smpPreSecond, UInt16 bitPerSample, int devId)
        {
            if (m_waveOutPtr != IntPtr.Zero)
            {
                return false;    // 重复初始化，不执行。
            }
            WinmmInterop.WAVEFORMATEX waveFormat;
            waveFormat.wFormatTag = WinmmInterop.WAVE_FORMAT_PCM;
            waveFormat.cbSize = 0;
            waveFormat.nChannels = chn;
            waveFormat.nSamplesPerSec = smpPreSecond;
            waveFormat.nBlockAlign = (ushort)(waveFormat.nChannels * bitPerSample / 8);
            waveFormat.nAvgBytesPerSec = (uint)smpPreSecond * (waveFormat.nBlockAlign);
            waveFormat.wBitsPerSample = bitPerSample;

            fixed (IntPtr* pWaveOut = &m_waveOutPtr)
            {
                uint openCode = WinmmInterop.waveOutOpen(pWaveOut, IntPtr.Add(IntPtr.Zero, devId), &waveFormat, m_waveOutPtrCallback, UIntPtr.Zero, WinmmInterop.CALLBACK_FUNCTION);
                if (openCode != WinmmInterop.MMSYSERR_NOERROR)
                {
                    m_waveOutPtr = IntPtr.Zero;
                    return false;
                }
            }
            m_closed = false;
            return true;
        }

        /// <summary>
        /// 处理回调。
        /// </summary>
        /// <param name="waveOutPtr"></param>
        /// <param name="uMsg"></param>
        /// <param name="dwInstance"></param>
        /// <param name="dwParam1"></param>
        /// <param name="dwParam2"></param>
        void ProcCallback(IntPtr waveOutPtr, uint uMsg, UIntPtr dwInstance, IntPtr dwParam1, IntPtr dwParam2)
        {
            if (uMsg == WinmmInterop.WOM_DONE)
            {
                WinmmInterop.WAVEHDR hdr = (WinmmInterop.WAVEHDR)Marshal.PtrToStructure(dwParam1, typeof(WinmmInterop.WAVEHDR));
                // 释放！
                Marshal.FreeHGlobal(hdr.lpData);
                Marshal.FreeHGlobal(dwParam1);
                Interlocked.Decrement(ref m_unhandleDataCounter);
            }
            else if (uMsg == WinmmInterop.WOM_CLOSE)
            {
                m_closed = true;
            }
        }
        /// <summary>
        /// 添加待播放的数据。
        /// </summary>
        /// <param name="data">待播放的数据流</param>
        /// <param name="startIndex">起始索引</param>
        /// <param name="dataLen">数据长度</param> 
        /// <returns>数据写入播放缓存返回true，否则返回false</returns>
        public bool Play(byte[] data, int startIndex, int dataLen)
        {
            if (m_waveOutPtr == IntPtr.Zero || m_closed == true)
            {
                return false;
            }
            if (m_unhandleDataCounter > m_maxAllowUnhandleData)
            {
                return false;
            }

            // 播放结果
            bool playResult = false;
            // 创建数据指针
            IntPtr dataPtr = Marshal.AllocHGlobal(data.Length);
            Marshal.Copy(data, 0, dataPtr, data.Length);
            // 创建wave头
            WinmmInterop.WAVEHDR waveHead = new WinmmInterop.WAVEHDR();
            waveHead.lpData = dataPtr;
            waveHead.dwBufferLength = (uint)data.Length;

            uint prepareOutRet = WinmmInterop.waveOutPrepareHeader(m_waveOutPtr, ref waveHead, (uint)sizeof(WinmmInterop.WAVEHDR));
            if (prepareOutRet == WinmmInterop.MMSYSERR_NOERROR)
            {
                IntPtr waveHeadPtr = Marshal.AllocHGlobal(sizeof(WinmmInterop.WAVEHDR));
                Marshal.StructureToPtr(waveHead, waveHeadPtr, true);

                uint writreRet = WinmmInterop.waveOutWrite(m_waveOutPtr, waveHeadPtr, (uint)sizeof(WinmmInterop.WAVEHDR));
                if (writreRet != WinmmInterop.MMSYSERR_NOERROR)
                {

                    Marshal.FreeHGlobal(dataPtr);
                    Marshal.FreeHGlobal(waveHeadPtr);
                }
                else
                {
                    Interlocked.Increment(ref m_unhandleDataCounter);
                    playResult = true;
                }
            }
            return playResult;
        }

        /// <summary>
        /// 设置播放设备音量。
        /// </summary>
        /// <param name="leftVolume">左声道音量</param>
        /// <param name="rightVolume">右声道音量</param>
        /// <returns></returns>
        public uint SetVolume(ushort leftVolume, ushort rightVolume)
        {
            uint ret = uint.MaxValue;
            if (IntPtr.Zero != m_waveOutPtr)
            {
                uint nActVol = (uint)(leftVolume + (rightVolume << 16));
                ret = WinmmInterop.waveOutSetVolume(m_waveOutPtr, nActVol);
            }
            return (ret);
        }


        /// <summary>
        /// 获取播放设备音量 0~0xffff 
        /// </summary>
        /// <param name="leftVolume">左声道音量</param>
        /// <param name="rightVolume">左声道音量</param>
        /// <returns></returns>
        public uint GetVolume(ref ushort leftVolume, ref ushort rightVolume)
        {
            uint ret = uint.MaxValue;
            if (IntPtr.Zero != m_waveOutPtr)
            {
                uint volumeVal = 0;
                ret = WinmmInterop.waveOutGetVolume(m_waveOutPtr, ref volumeVal);
                if (ret == WinmmInterop.MMSYSERR_NOERROR)
                {
                    leftVolume = (ushort)(volumeVal & 0x0000ffff);
                    rightVolume = (ushort)(volumeVal >> 16);
                }
            }
            return ret;
        }

        /// <summary> 
        /// 重设播放设备。
        /// </summary>
        /// <returns></returns>
        public uint Reset()
        {
            uint ret = uint.MaxValue;
            if (m_waveOutPtr != IntPtr.Zero)
            {
                ret = WinmmInterop.waveOutReset(m_waveOutPtr);
            }
            return ret;
        }
        /// <summary>
        /// 释放资源。
        /// </summary>
        public void Dispose()
        {
            if (m_waveOutPtr != IntPtr.Zero)
            {
                WinmmInterop.waveOutReset(m_waveOutPtr);
                WinmmInterop.waveOutClose(m_waveOutPtr);
                m_waveOutPtr = IntPtr.Zero;
            }
        }
    }
}
